/*
 ------------------------------------------------------------------------------
 Copyright (C) 2006-2007 Team Blur.

 This file is part of the Quantum Engine source code.

 The Quantum Engine source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The Quantum Engine source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the Quantum Engine source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


#include "cm_local.h"


clipMap_t	cm;

cmStats_t	cm_stats;

cvar_t *	cm_skipSurfaces;
cvar_t *	cm_skipModels;
cvar_t *	cm_skipPVS;
cvar_t *	cm_skipAreas;
cvar_t *	cm_showStats;


/*
 ==============================================================================

 MAP LOADING

 ==============================================================================
*/


/*
 ==================
 CM_LoadMaterials
 ==================
*/
static void CM_LoadMaterials (const byte *data, const bspLump_t *lump){

	bspMaterial_t	*in;
	cmMaterial_t	*out;
	int				i;

	in = (bspMaterial_t *)(data + lump->offset);
	if (lump->length % sizeof(bspMaterial_t))
		Com_Error(false, "CM_LoadMap: funny lump size (%s)", cm.name);

	cm.numMaterials = lump->length / sizeof(bspMaterial_t);
	if (cm.numMaterials < 1)
		Com_Error(false, "CM_LoadMap: no materials (%s)", cm.name);
	else if (cm.numMaterials > BSP_MAX_MATERIALS)
		Com_Error(false, "CM_LoadMap: too many materials (%s)", cm.name);

	cm.materials = out = (cmMaterial_t *)Mem_Alloc((cm.numMaterials + BOX_MODEL_MATERIALS) * sizeof(cmMaterial_t), TAG_COLLISION);
	cm.size += (cm.numMaterials + BOX_MODEL_MATERIALS) * sizeof(cmMaterial_t);

	for (i = 0; i < cm.numMaterials; i++, in++, out++){
		Str_Copy(out->name, in->name, sizeof(out->name));

		out->surfaceFlags = LittleLong(in->surfaceFlags);
		out->contentFlags = LittleLong(in->contentFlags);
	}
}

/*
 ==================
 CM_LoadPlanes
 ==================
*/
static void CM_LoadPlanes (const byte *data, const bspLump_t *lump){

	bspPlane_t	*in;
	odPlane		*out;
	int			i;

	in = (bspPlane_t *)(data + lump->offset);
	if (lump->length % sizeof(bspPlane_t))
		Com_Error(false, "CM_LoadMap: funny lump size (%s)", cm.name);

	cm.numPlanes = lump->length / sizeof(bspPlane_t);
	if (cm.numPlanes < 1)
		Com_Error(false, "CM_LoadMap: no planes (%s)", cm.name);
	else if (cm.numPlanes > BSP_MAX_PLANES)
		Com_Error(false, "CM_LoadMap: too many planes (%s)", cm.name);

	cm.planes = out = (odPlane *)Mem_Alloc((cm.numPlanes + BOX_MODEL_PLANES) * sizeof(odPlane), TAG_COLLISION);
	cm.size += (cm.numPlanes + BOX_MODEL_PLANES) * sizeof(odPlane);

	for (i = 0; i < cm.numPlanes; i++, in++, out++){
		out->normal[0] = LittleFloat(in->normal[0]);
		out->normal[1] = LittleFloat(in->normal[1]);
		out->normal[2] = LittleFloat(in->normal[2]);

		out->dist = LittleFloat(in->dist);

		out->SetType();
		out->SetSignBits();
	}
}

/*
 ==================
 CM_LoadBrushSides
 ==================
*/
static void CM_LoadBrushSides (const byte *data, const bspLump_t *lump){

	bspBrushSide_t	*in;
	cmBrushSide_t	*out;
	int				i;

	in = (bspBrushSide_t *)(data + lump->offset);
	if (lump->length % sizeof(bspBrushSide_t))
		Com_Error(false, "CM_LoadMap: funny lump size (%s)", cm.name);

	cm.numBrushSides = lump->length / sizeof(bspBrushSide_t);
	if (cm.numBrushSides < 1)
		Com_Error(false, "CM_LoadMap: no brush sides (%s)", cm.name);
	else if (cm.numBrushSides > BSP_MAX_BRUSHSIDES)
		Com_Error(false, "CM_LoadMap: too many brush sides (%s)", cm.name);

	cm.brushSides = out = (cmBrushSide_t *)Mem_Alloc((cm.numBrushSides + BOX_MODEL_BRUSHSIDES) * sizeof(cmBrushSide_t), TAG_COLLISION);
	cm.size += (cm.numBrushSides + BOX_MODEL_BRUSHSIDES) * sizeof(cmBrushSide_t);

	for (i = 0; i < cm.numBrushSides; i++, in++, out++){
		out->material = cm.materials + LittleLong(in->materialNum);

		out->plane = cm.planes + LittleLong(in->planeNum);
	}
}

/*
 ==================
 CM_LoadBrushes
 ==================
*/
static void CM_LoadBrushes (const byte *data, const bspLump_t *lump){

	bspBrush_t	*in;
	cmBrush_t	*out;
	int			i;

	in = (bspBrush_t *)(data + lump->offset);
	if (lump->length % sizeof(bspBrush_t))
		Com_Error(false, "CM_LoadMap: funny lump size (%s)", cm.name);

	cm.numBrushes = lump->length / sizeof(bspBrush_t);
	if (cm.numBrushes < 1)
		Com_Error(false, "CM_LoadMap: no brushes (%s)", cm.name);
	else if (cm.numBrushes > BSP_MAX_BRUSHES)
		Com_Error(false, "CM_LoadMap: too many brushes (%s)", cm.name);

	cm.brushes = out = (cmBrush_t *)Mem_Alloc((cm.numBrushes + BOX_MODEL_BRUSHES) * sizeof(cmBrush_t), TAG_COLLISION);
	cm.size += (cm.numBrushes + BOX_MODEL_BRUSHES) * sizeof(cmBrush_t);

	for (i = 0; i < cm.numBrushes; i++, in++, out++){
		out->material = cm.materials + LittleLong(in->materialNum);

		out->numSides = LittleLong(in->numSides);
		out->sides = cm.brushSides + LittleLong(in->firstSide);

		out->bounds[0][0] = -out->sides[0].plane->dist;
		out->bounds[0][1] = -out->sides[2].plane->dist;
		out->bounds[0][2] = -out->sides[4].plane->dist;

		out->bounds[1][0] = out->sides[1].plane->dist;
		out->bounds[1][1] = out->sides[3].plane->dist;
		out->bounds[1][2] = out->sides[5].plane->dist;

		// Clear counters
		out->contentsCount = 0;
		out->traceCount = 0;
	}
}

/*
 ==================
 CM_LoadSurfacePlanes
 ==================
*/
static void CM_LoadSurfacePlanes (const byte *data, const bspLump_t *lump){

	bspPlane_t	*in;
	odPlane		*out;
	int			i;

	in = (bspPlane_t *)(data + lump->offset);
	if (lump->length % sizeof(bspPlane_t))
		Com_Error(false, "CM_LoadMap: funny lump size (%s)", cm.name);

	cm.numSurfacePlanes = lump->length / sizeof(bspPlane_t);
	if (cm.numSurfacePlanes < 1)
		return;
	else if (cm.numSurfacePlanes > BSP_MAX_SURFACEPLANES)
		Com_Error(false, "CM_LoadMap: too many surface planes (%s)", cm.name);

	cm.surfacePlanes = out = (odPlane *)Mem_Alloc(cm.numSurfacePlanes * sizeof(odPlane), TAG_COLLISION);
	cm.size += cm.numSurfacePlanes * sizeof(odPlane);

	for (i = 0; i < cm.numSurfacePlanes; i++, in++, out++){
		out->normal[0] = LittleFloat(in->normal[0]);
		out->normal[1] = LittleFloat(in->normal[1]);
		out->normal[2] = LittleFloat(in->normal[2]);

		out->dist = LittleFloat(in->dist);

		out->SetType();
		out->SetSignBits();
	}
}

/*
 ==================
 CM_LoadSurfaceFacets
 ==================
*/
static void CM_LoadSurfaceFacets (const byte *data, const bspLump_t *lump){

	bspSurfaceFacet_t	*in;
	cmSurfaceFacet_t	*out;
	int					i, j;

	in = (bspSurfaceFacet_t *)(data + lump->offset);
	if (lump->length % sizeof(bspSurfaceFacet_t))
		Com_Error(false, "CM_LoadMap: funny lump size (%s)", cm.name);

	cm.numSurfaceFacets = lump->length / sizeof(bspSurfaceFacet_t);
	if (cm.numSurfaceFacets < 1)
		return;
	else if (cm.numSurfaceFacets > BSP_MAX_SURFACEFACETS)
		Com_Error(false, "CM_LoadMap: too many surface facets (%s)", cm.name);

	cm.surfaceFacets = out = (cmSurfaceFacet_t *)Mem_Alloc(cm.numSurfaceFacets * sizeof(cmSurfaceFacet_t), TAG_COLLISION);
	cm.size += cm.numSurfaceFacets * sizeof(cmSurfaceFacet_t);

	for (i = 0; i < cm.numSurfaceFacets; i++, in++, out++){
		out->facetPlane = LittleLong(in->facetPlane);

		out->numBorders = LittleLong(in->numBorders);
		out->borderInward = LittleLong(in->borderInward);

		for (j = 0; j < 26; j++)
			out->borderPlanes[j] = LittleShort(in->borderPlanes[j]);
	}
}

/*
 ==================
 CM_LoadSurfaces
 ==================
*/
static void CM_LoadSurfaces (const byte *data, const bspLump_t *lump){

	bspSurface_t	*in;
	cmSurface_t		*out;
	surfaceType_t	type;
	int				i, j;

	in = (bspSurface_t *)(data + lump->offset);
	if (lump->length % sizeof(bspSurface_t))
		Com_Error(false, "CM_LoadMap: funny lump size (%s)", cm.name);

	cm.numSurfaces = lump->length / sizeof(bspSurface_t);
	if (cm.numSurfaces < 1)
		Com_Error(false, "CM_LoadMap: no surfaces (%s)", cm.name);
	else if (cm.numSurfaces > BSP_MAX_SURFACES)
		Com_Error(false, "CM_LoadMap: too many surfaces (%s)", cm.name);

	cm.surfaces = (cmSurface_t **)Mem_Alloc(cm.numSurfaces * sizeof(cmSurface_t *), TAG_COLLISION);
	cm.size += cm.numSurfaces * sizeof(cmSurface_t *);

	// Scan through all the surfaces, but only load the ones with collision
	// data
	for (i = 0; i < cm.numSurfaces; i++, in++){
		type = (surfaceType_t)LittleLong(in->type);

		if (type != ST_PATCH && type != ST_TRISOUP){
			cm.surfaces[i] = NULL;
			continue;
		}

		if (LittleLong(in->firstPlane) == -1 || LittleLong(in->firstFacet) == -1){
			cm.surfaces[i] = NULL;
			continue;
		}

		cm.surfaces[i] = out = (cmSurface_t *)Mem_Alloc(sizeof(cmSurface_t), TAG_COLLISION);
		cm.size += sizeof(cmSurface_t);

		out->type = type;

		out->material = cm.materials + LittleLong(in->materialNum);

		for (j = 0; j < 3; j++){
			out->bounds[0][j] = LittleFloat(in->bounds[0][j]);
			out->bounds[1][j] = LittleFloat(in->bounds[1][j]);
		}

		for (j = 0; j < 6; j++){
			out->sides[j].normal.Clear();

			if (j & 1){
				out->sides[j].normal[j>>1] = -1.0f;
				out->sides[j].dist = -out->bounds[0][j>>1];
			}
			else {
				out->sides[j].normal[j>>1] = 1.0f;
				out->sides[j].dist = out->bounds[1][j>>1];
			}

			out->sides[j].SetType();
			out->sides[j].SetSignBits();
		}

		out->numPlanes = LittleLong(in->numPlanes);
		out->planes = cm.surfacePlanes + LittleLong(in->firstPlane);

		out->numFacets = LittleLong(in->numFacets);
		out->facets = cm.surfaceFacets + LittleLong(in->firstFacet);

		// Clear counters
		out->contentsCount = 0;
		out->traceCount = 0;
	}
}

/*
 ==================
 CM_LoadLeafBrushes
 ==================
*/
static void CM_LoadLeafBrushes (const byte *data, const bspLump_t *lump){

	int		*in;
	int		*out;
	int		i;

	in = (int *)(data + lump->offset);
	if (lump->length % sizeof(int))
		Com_Error(false, "CM_LoadMap: funny lump size (%s)", cm.name);

	cm.numLeafBrushes = lump->length / sizeof(int);
	if (cm.numLeafBrushes < 1)
		Com_Error(false, "CM_LoadMap: no leaf brushes (%s)", cm.name);
	else if (cm.numLeafBrushes > BSP_MAX_LEAFBRUSHES)
		Com_Error(false, "CM_LoadMap: too many leaf brushes (%s)", cm.name);

	cm.leafBrushes = out = (int *)Mem_Alloc((cm.numLeafBrushes + BOX_MODEL_BRUSHES) * sizeof(int), TAG_COLLISION);
	cm.size += (cm.numLeafBrushes + BOX_MODEL_BRUSHES) * sizeof(int);

	for (i = 0; i < cm.numLeafBrushes; i++, in++, out++)
		*out = LittleLong(*in);
}

/*
 ==================
 CM_LoadLeafSurfaces
 ==================
*/
static void CM_LoadLeafSurfaces (const byte *data, const bspLump_t *lump){

	int		*in;
	int		*out;
	int		i;

	in = (int *)(data + lump->offset);
	if (lump->length % sizeof(int))
		Com_Error(false, "CM_LoadMap: funny lump size (%s)", cm.name);

	cm.numLeafSurfaces = lump->length / sizeof(int);
	if (cm.numLeafSurfaces < 1)
		Com_Error(false, "CM_LoadMap: no leaf surfaces (%s)", cm.name);
	else if (cm.numLeafSurfaces > BSP_MAX_LEAFSURFACES)
		Com_Error(false, "CM_LoadMap: too many leaf surfaces (%s)", cm.name);

	cm.leafSurfaces = out = (int *)Mem_Alloc(cm.numLeafSurfaces * sizeof(int), TAG_COLLISION);
	cm.size += cm.numLeafSurfaces * sizeof(int);

	for (i = 0; i < cm.numLeafSurfaces; i++, in++, out++)
		*out = LittleLong(*in);
}

/*
 ==================
 CM_LoadLeafs
 ==================
*/
static void CM_LoadLeafs (const byte *data, const bspLump_t *lump){

	bspLeaf_t	*in;
	cmLeaf_t	*out;
	cmBrush_t	*brush;
	cmSurface_t	*surface;
	int			i, j, num;

	in = (bspLeaf_t *)(data + lump->offset);
	if (lump->length % sizeof(bspLeaf_t))
		Com_Error(false, "CM_LoadMap: funny lump size (%s)", cm.name);

	cm.numLeafs = lump->length / sizeof(bspLeaf_t);
	if (cm.numLeafs < 1)
		Com_Error(false, "CM_LoadMap: no leafs (%s)", cm.name);
	else if (cm.numLeafs > BSP_MAX_LEAFS)
		Com_Error(false, "CM_LoadMap: too many leafs (%s)", cm.name);

	cm.leafs = out = (cmLeaf_t *)Mem_Alloc(cm.numLeafs * sizeof(cmLeaf_t), TAG_COLLISION);
	cm.size += cm.numLeafs * sizeof(cmLeaf_t);

	for (i = 0; i < cm.numLeafs; i++, in++, out++){
		out->cluster = LittleLong(in->cluster);
		out->area = LittleLong(in->area);

		out->canSeePortalSky = LittleLong(in->canSeePortalSky);

		out->contentFlags = 0;

		out->numLeafBrushes = LittleLong(in->numLeafBrushes);
		out->firstLeafBrush = LittleLong(in->firstLeafBrush);

		out->numLeafSurfaces = LittleLong(in->numLeafSurfaces);
		out->firstLeafSurface = LittleLong(in->firstLeafSurface);

		// OR in the content flags from all the leaf brushes
		for (j = 0; j < out->numLeafBrushes; j++){
			num = cm.leafBrushes[out->firstLeafBrush + j];
			brush = &cm.brushes[num];

			out->contentFlags |= brush->material->contentFlags;
		}

		// OR in the content flags from all the leaf surfaces
		for (j = 0; j < out->numLeafSurfaces; j++){
			num = cm.leafSurfaces[out->firstLeafSurface + j];
			surface = cm.surfaces[num];

			if (!surface)
				continue;		// No collision data

			out->contentFlags |= surface->material->contentFlags;
		}

		// Count clusters
		if (out->cluster >= cm.numClusters)
			cm.numClusters = out->cluster + 1;
	}

	// Calculate cluster bytes
	cm.clusterBytes = ALIGN(cm.numClusters, 64) >> 3;
}

/*
 ==================
 CM_LoadNodes
 ==================
*/
static void CM_LoadNodes (const byte *data, const bspLump_t *lump){

	bspNode_t	*in;
	cmNode_t	*out;
	int			i;

	in = (bspNode_t *)(data + lump->offset);
	if (lump->length % sizeof(bspNode_t))
		Com_Error(false, "CM_LoadMap: funny lump size (%s)", cm.name);

	cm.numNodes = lump->length / sizeof(bspNode_t);
	if (cm.numNodes < 1)
		Com_Error(false, "CM_LoadMap: no nodes (%s)", cm.name);
	else if (cm.numNodes > BSP_MAX_NODES)
		Com_Error(false, "CM_LoadMap: too many nodes (%s)", cm.name);

	cm.nodes = out = (cmNode_t *)Mem_Alloc(cm.numNodes * sizeof(cmNode_t), TAG_COLLISION);
	cm.size += cm.numNodes * sizeof(cmNode_t);

	for (i = 0; i < cm.numNodes; i++, in++, out++){
		out->plane = cm.planes + LittleLong(in->planeNum);

		out->children[0] = LittleLong(in->children[0]);
		out->children[1] = LittleLong(in->children[1]);
	}
}

/*
 ==================
 CM_LoadInlineModels
 ==================
*/
static void CM_LoadInlineModels (const byte *data, const bspLump_t *lump){

	bspInlineModel_t	*in;
	cmInlineModel_t		*out;
	cmBrush_t			*brush;
	cmSurface_t			*surface;
	int					*indices, first;
	int					i, j, num;

	in = (bspInlineModel_t *)(data + lump->offset);
	if (lump->length % sizeof(bspInlineModel_t))
		Com_Error(false, "CM_LoadMap: funny lump size (%s)", cm.name);

	cm.numInlineModels = lump->length / sizeof(bspInlineModel_t);
	if (cm.numInlineModels < 1)
		Com_Error(false, "CM_LoadMap: no inline models (%s)", cm.name);
	else if (cm.numInlineModels > BSP_MAX_INLINEMODELS)
		Com_Error(false, "CM_LoadMap: too many inline models (%s)", cm.name);

	cm.inlineModels = out = (cmInlineModel_t *)Mem_Alloc(cm.numInlineModels * sizeof(cmInlineModel_t), TAG_COLLISION);
	cm.size += cm.numInlineModels * sizeof(cmInlineModel_t);

	for (i = 0; i < cm.numInlineModels; i++, in++, out++){
		for (j = 0; j < 3; j++){
			out->bounds[0][j] = LittleFloat(in->bounds[0][j]);
			out->bounds[1][j] = LittleFloat(in->bounds[1][j]);
		}

		for (j = 0; j < 6; j++){
			out->sides[j].normal.Clear();

			if (j & 1){
				out->sides[j].normal[j>>1] = -1.0f;
				out->sides[j].dist = -out->bounds[0][j>>1];
			}
			else {
				out->sides[j].normal[j>>1] = 1.0f;
				out->sides[j].dist = out->bounds[1][j>>1];
			}

			out->sides[j].SetType();
			out->sides[j].SetSignBits();
		}

		if (i == 0){
			// World model doesn't need other info
			memset(&out->leaf, 0, sizeof(cmLeaf_t));

			// Load the inline model
			CM_LoadInlineModel(i, out);
			continue;
		}

		// Make a leaf just to hold the model's brushes and surfaces
		out->leaf.cluster = 0;
		out->leaf.area = 0;

		out->leaf.canSeePortalSky = false;

		out->leaf.contentFlags = 0;

		out->leaf.numLeafBrushes = LittleLong(in->numBrushes);
		out->leaf.numLeafSurfaces = LittleLong(in->numSurfaces);

		indices = (int *)Mem_Alloc(out->leaf.numLeafBrushes * sizeof(int), TAG_COLLISION);
		cm.size += out->leaf.numLeafBrushes * sizeof(int);

		out->leaf.firstLeafBrush = indices - cm.leafBrushes;

		first = LittleLong(in->firstBrush);
		for (j = 0; j < out->leaf.numLeafBrushes; j++)
			indices[j] = first + j;

		indices = (int *)Mem_Alloc(out->leaf.numLeafSurfaces * sizeof(int), TAG_COLLISION);
		cm.size += out->leaf.numLeafSurfaces * sizeof(int);

		out->leaf.firstLeafSurface = indices - cm.leafSurfaces;

		first = LittleLong(in->firstSurface);
		for (j = 0; j < out->leaf.numLeafSurfaces; j++)
			indices[j] = first + j;

		// OR in the content flags from all the model brushes
		for (j = 0; j < out->leaf.numLeafBrushes; j++){
			num = cm.leafBrushes[out->leaf.firstLeafBrush + j];
			brush = &cm.brushes[num];

			out->leaf.contentFlags |= brush->material->contentFlags;
		}

		// OR in the content flags from all the model surfaces
		for (j = 0; j < out->leaf.numLeafSurfaces; j++){
			num = cm.leafSurfaces[out->leaf.firstLeafSurface + j];
			surface = cm.surfaces[num];

			if (!surface)
				continue;		// No collision data

			out->leaf.contentFlags |= surface->material->contentFlags;
		}

		// Load the inline model
		CM_LoadInlineModel(i, out);
	}
}

/*
 ==================
 CM_LoadPVS
 ==================
*/
static void CM_LoadPVS (const byte *data, const bspLump_t *lump){

	cm.noPVS = (byte *)Mem_Alloc(cm.clusterBytes, TAG_COLLISION);
	cm.size += cm.clusterBytes;

	memset(cm.noPVS, 0xFF, cm.clusterBytes);

	if (!lump->length){
		cm.pvs = NULL;
		return;
	}

	if (lump->length > BSP_MAX_PVS)
		Com_Error(false, "CM_LoadMap: too large PVS (%s)", cm.name);

	cm.pvs = (byte *)Mem_Alloc(lump->length, TAG_COLLISION);
	cm.size += lump->length;

	memcpy(cm.pvs, data + lump->offset, lump->length);
}

/*
 ==================
 CM_LoadPortals
 ==================
*/
static void CM_LoadPortals (const byte *data, const bspLump_t *lump){

	bspPortal_t	*in;
	cmPortal_t	*out;
	int			i;

	in = (bspPortal_t *)(data + lump->offset);
	if (lump->length % sizeof(bspPortal_t))
		Com_Error(false, "CM_LoadMap: funny lump size (%s)", cm.name);

	cm.numPortals = lump->length / sizeof(bspPortal_t);
	if (cm.numPortals < 1)
		return;
	else if (cm.numPortals > BSP_MAX_PORTALS)
		Com_Error(false, "CM_LoadMap: too many portals (%s)", cm.name);

	cm.portals = out = (cmPortal_t *)Mem_Alloc(cm.numPortals * sizeof(cmPortal_t), TAG_COLLISION);
	cm.size += cm.numPortals * sizeof(cmPortal_t);

	for (i = 0; i < cm.numPortals; i++, in++, out++){
		out->portalNum = LittleLong(in->portalNum);

		out->areas[0] = LittleLong(in->areas[0]);
		out->areas[1] = LittleLong(in->areas[1]);

		// Clear open
		out->open = false;
	}
}

/*
 ==================
 CM_LoadAreaPortals
 ==================
*/
static void CM_LoadAreaPortals (const byte *data, const bspLump_t *lump){

	int		*in;
	int		*out;
	int		i;

	in = (int *)(data + lump->offset);
	if (lump->length % sizeof(int))
		Com_Error(false, "CM_LoadMap: funny lump size (%s)", cm.name);

	cm.numAreaPortals = lump->length / sizeof(int);
	if (cm.numAreaPortals < 1)
		return;
	else if (cm.numAreaPortals > BSP_MAX_AREAPORTALS)
		Com_Error(false, "CM_LoadMap: too many area portals (%s)", cm.name);

	cm.areaPortals = out = (int *)Mem_Alloc(cm.numAreaPortals * sizeof(int), TAG_COLLISION);
	cm.size += cm.numAreaPortals * sizeof(int);

	for (i = 0; i < cm.numAreaPortals; i++, in++, out++)
		*out = LittleLong(*in);
}

/*
 ==================
 CM_LoadAreas
 ==================
*/
static void CM_LoadAreas (const byte *data, const bspLump_t *lump){

	bspArea_t	*in;
	cmArea_t	*out;
	int			i;

	in = (bspArea_t *)(data + lump->offset);
	if (lump->length % sizeof(bspArea_t))
		Com_Error(false, "CM_LoadMap: funny lump size (%s)", cm.name);

	cm.numAreas = lump->length / sizeof(bspArea_t);
	if (cm.numAreas < 1)
		Com_Error(false, "CM_LoadMap: no areas (%s)", cm.name);
	else if (cm.numAreas > BSP_MAX_AREAS)
		Com_Error(false, "CM_LoadMap: too many areas (%s)", cm.name);

	cm.areas = out = (cmArea_t *)Mem_Alloc(cm.numAreas * sizeof(cmArea_t), TAG_COLLISION);
	cm.size += cm.numAreas * sizeof(cmArea_t);

	for (i = 0; i < cm.numAreas; i++, in++, out++){
		out->numAreaPortals = LittleLong(in->numAreaPortals);
		out->firstAreaPortal = LittleLong(in->firstAreaPortal);

		// Clear flood
		out->floodNum = 0;
		out->floodCount = 0;
	}
}

/*
 ==================
 CM_LoadMap
 ==================
*/
void CM_LoadMap (const char *name){

	bspHeader_t	*header;
	byte		*data;
	int			i;

	// Check if already loaded
	if (cm.loaded){
		if (!Str_ICompare(cm.name, name))
			return;

		Com_Error(false, "CM_LoadMap: can't load '%s' because another clip map is already loaded", name);
	}

	// Load the file
	FS_ReadFile(name, (void **)&data);
	if (!data)
		Com_Error(false, "CM_LoadMap: '%s' not found", name);

	// Fill it in
	cm.loaded = true;
	Str_Copy(cm.name, name, sizeof(cm.name));

	// Byte swap the header fields and sanity check
	header = (bspHeader_t *)data;

	for (i = 0; i < sizeof(bspHeader_t) / 4; i++)
		((int *)header)[i] = LittleLong(((int *)header)[i]);

	if (header->id != BSP_IDENT)
		Com_Error(false, "CM_LoadMap: wrong file id (%s)", cm.name);

	if (header->version != BSP_VERSION)
		Com_Error(false, "CM_LoadMap: wrong version number (%i should be %i) (%s)", header->version, BSP_VERSION, cm.name);

	// Load into heap
	CM_LoadMaterials(data, &header->lumps[LUMP_MATERIALS]);
	CM_LoadPlanes(data, &header->lumps[LUMP_PLANES]);
	CM_LoadBrushSides(data, &header->lumps[LUMP_BRUSHSIDES]);
	CM_LoadBrushes(data, &header->lumps[LUMP_BRUSHES]);
	CM_LoadSurfacePlanes(data, &header->lumps[LUMP_SURFACEPLANES]);
	CM_LoadSurfaceFacets(data, &header->lumps[LUMP_SURFACEFACETS]);
	CM_LoadSurfaces(data, &header->lumps[LUMP_SURFACES]);
	CM_LoadLeafBrushes(data, &header->lumps[LUMP_LEAFBRUSHES]);
	CM_LoadLeafSurfaces(data, &header->lumps[LUMP_LEAFSURFACES]);
	CM_LoadLeafs(data, &header->lumps[LUMP_LEAFS]);
	CM_LoadNodes(data, &header->lumps[LUMP_NODES]);
	CM_LoadInlineModels(data, &header->lumps[LUMP_INLINEMODELS]);
	CM_LoadPVS(data, &header->lumps[LUMP_PVS]);
	CM_LoadPortals(data, &header->lumps[LUMP_PORTALS]);
	CM_LoadAreaPortals(data, &header->lumps[LUMP_AREAPORTALS]);
	CM_LoadAreas(data, &header->lumps[LUMP_AREAS]);

	// Free file data
	FS_FreeFile(data);

	// Set up some needed things
	CM_InitBoxModel();
	CM_FloodAreaConnections();
}

/*
 ==================
 CM_FreeMap
 ==================
*/
void CM_FreeMap (void){

	CM_FreeModels();

	Mem_TagFree(TAG_COLLISION);

	memset(&cm, 0, sizeof(clipMap_t));

	memset(&cm_stats, 0, sizeof(cmStats_t));
}


// ============================================================================


/*
 ==================
 CM_PrintStats
 ==================
*/
void CM_PrintStats (void){

	if (!cm.loaded)
		return;

	switch (cm_showStats->integerValue){
	case 1:
		Com_Printf("contents:%i traces:%i tests:%i points:%i pvs:%i areas:%i\n", cm_stats.contents, cm_stats.traces, cm_stats.tests, cm_stats.leafPoints, cm_stats.pvsPoints + cm_stats.pvsBounds, cm_stats.areaPoints + cm_stats.areaBounds);
		break;
	case 2:
		Com_Printf("contents:%i (leaf:%i brush:%i patch:%i trisoup:%i mesh:%i)\n", cm_stats.contents, cm_stats.leafContents, cm_stats.brushContents, cm_stats.patchContents, cm_stats.trisoupContents, cm_stats.meshContents);
		break;
	case 3:
		Com_Printf("traces:%i (leaf:%i brush:%i patch:%i trisoup:%i mesh:%i)\n", cm_stats.traces, cm_stats.leafTraces, cm_stats.brushTraces, cm_stats.patchTraces, cm_stats.trisoupTraces, cm_stats.meshTraces);
		break;
	case 4:
		Com_Printf("tests:%i (leaf:%i brush:%i patch:%i trisoup:%i mesh:%i)\n", cm_stats.tests, cm_stats.leafTests, cm_stats.brushTests, cm_stats.patchTests, cm_stats.trisoupTests, cm_stats.meshTests);
		break;
	case 5:
		Com_Printf("pvs:%i (points:%i bounds:%i)\n", cm_stats.pvsPoints + cm_stats.pvsBounds, cm_stats.pvsPoints, cm_stats.pvsBounds);
		break;
	case 6:
		Com_Printf("areas:%i (points:%i bounds:%i)\n", cm_stats.areaPoints + cm_stats.areaBounds, cm_stats.areaPoints, cm_stats.areaBounds);
		break;
	}

	// Clear for next frame
	memset(&cm_stats, 0, sizeof(cmStats_t));
}

/*
 ==================
 CM_Init
 ==================
*/
void CM_Init (void){

	cm_skipSurfaces = CVar_Get("cm_skipSurfaces", "0", CVAR_CHEAT, "Skip collisions against surfaces");
	cm_skipModels = CVar_Get("cm_skipModels", "0", CVAR_CHEAT, "Skip collisions against models");
	cm_skipPVS = CVar_Get("cm_skipPVS", "0", CVAR_CHEAT, "Skip the PVS");
	cm_skipAreas = CVar_Get("cm_skipAreas", "0", CVAR_CHEAT, "Skip area connections");
	cm_showStats = CVar_Get("cm_showStats", "0", CVAR_CHEAT, "Show collision statistics (1 = totals, 2 = contents, 3 = traces, 4 = tests, 5 = PVS, 6 = areas)");

	CM_InitModels();
}

/*
 ==================
 CM_Shutdown
 ==================
*/
void CM_Shutdown (void){

	CM_ShutdownModels();

	Mem_TagFree(TAG_COLLISION);

	memset(&cm, 0, sizeof(clipMap_t));

	memset(&cm_stats, 0, sizeof(cmStats_t));
}
